/**
 * 
 */
package qy.jalgotrade.technical.stats;

import org.apache.commons.math3.stat.StatUtils;
import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation;

import it.unimi.dsi.fastutil.doubles.DoubleList;
import qy.jalgotrade.dataseries.SequenceDataSeries;
import qy.jalgotrade.technical.EventBasedFilter;
import qy.jalgotrade.technical.EventWindow;

/**
 * Z-Score filter.
 * 
 * @author qy
 *
 */
public class ZScore extends EventBasedFilter<Double> {

	protected class ZScoreEventWindow extends EventWindow<Double> {

		private StandardDeviation stdDev;

		private int __ddof;

		/**
		 * 
		 * @param period
		 */
		public ZScoreEventWindow(int period) {

			this(period, 0);
		}

		/**
		 * 
		 * @param period
		 * @param ddof
		 */
		public ZScoreEventWindow(int period, int ddof) {

			super(period, Double.class);
			assert period > 0;
			__ddof = ddof;
			if (__ddof == 0) {
				stdDev = new StandardDeviation(false);
			} else {
				stdDev = new StandardDeviation(true);
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see qy.jalgotrade.technical.EventBasedFilter.EventWindow#getValue()
		 */
		@Override
		public Double getValue() {

			double ret = Double.NaN;
			if (windowFull()) {
				DoubleList values = (DoubleList) getValues();
				if (values.size() > 0) {
					double lastValue = values.getDouble(values.size() - 1);
					double mean = StatUtils.mean(values.toDoubleArray());
					double std = stdDev.evaluate(values.toDoubleArray());
					ret = (lastValue - mean) / std;
				}
			}
			return ret;
		}
	}

	/**
	 * Z-Score filter.
	 * 
	 * @param  dataSeries The DataSeries instance being filtered.
	 * @param  period     The number of values to use to calculate the Z-Score.
	 * @throws Exception
	 */
	public ZScore(SequenceDataSeries<Double> dataSeries, int period) throws Exception {

		this(dataSeries, period, 0, 0);
	}

	/**
	 * Z-Score filter.
	 * 
	 * @param  dataSeries The DataSeries instance being filtered.
	 * @param  period     The number of values to use to calculate the Z-Score.
	 * @param  ddof       Delta degrees of freedom to use for the standard
	 *                    deviation.
	 * @throws Exception
	 */
	public ZScore(SequenceDataSeries<Double> dataSeries, int period, int ddof) throws Exception {

		this(dataSeries, period, ddof, 0);
	}

	/**
	 * Z-Score filter.
	 * 
	 * @param  dataSeries The DataSeries instance being filtered.
	 * @param  period     The number of values to use to calculate the Z-Score.
	 * @param  ddof       Delta degrees of freedom to use for the standard
	 *                    deviation.
	 * @param  maxLen     The maximum number of values to hold. Once a bounded
	 *                    length is full, when new items are added, a corresponding
	 *                    number of items are discarded from the opposite end. If
	 *                    None then dataseries.DEFAULT_MAX_LEN is used.
	 * @throws Exception
	 */
	public ZScore(SequenceDataSeries<Double> dataSeries, int period, int ddof, int maxLen) throws Exception {

		super(maxLen);
		ZScoreEventWindow win = new ZScoreEventWindow(period, ddof);
		_init(dataSeries, win);
	}
}
